using System.Collections.Generic;
using System.Linq;
using Guesshoo.TrainExpress.Extensions;
using QuickGraph;
using QuickGraph.Algorithms.RankedShortestPath;

namespace Guesshoo.TrainExpress
{
	public class RouteSpecification
	{
		private readonly BidirectionalGraph<string, StationLink> _graph;
		private readonly Dictionary<StationLink, int> _routeCost;

		#region ctor

		public RouteSpecification() {
			_routeCost = new Dictionary<StationLink, int>();
			_graph = new BidirectionalGraph<string, StationLink>();

			AddEdge(_graph, _routeCost, new StationLink("A", "B"), 3);
			AddEdge(_graph, _routeCost, new StationLink("B", "A"), 3);
			AddEdge(_graph, _routeCost, new StationLink("A", "D"), 6);
			AddEdge(_graph, _routeCost, new StationLink("B", "C"), 7);

			AddEdge(_graph, _routeCost, new StationLink("C", "D"), 8);
			AddEdge(_graph, _routeCost, new StationLink("D", "E"), 9);
			AddEdge(_graph, _routeCost, new StationLink("E", "D"), 9);
			AddEdge(_graph, _routeCost, new StationLink("D", "C"), 9);

			AddEdge(_graph, _routeCost, new StationLink("D", "B"), 5);
			AddEdge(_graph, _routeCost, new StationLink("C", "E"), 3);
		}

		#endregion

		public Station FindStation(string stationName) {
			Station station = null;
			if (_graph.ContainsVertex(stationName)) {
				station = new Station(stationName);
				station.IncomingStations.AddRange(GetIncomingStations(stationName));

				station.OutgoingStations.AddRange(GetOutgoingStations(stationName));
			}

			return station;
		}

		public IEnumerable<Route> FindAllRoutes(string start, string end) {
			var algo = new HoffmanPavleyRankedShortestPathAlgorithm<string, StationLink>(
				_graph, e => 1);
			algo.ShortestPathCount = 10;
			algo.Compute(start, end);

			return algo.ComputedShortestPaths.ToList()
				.ConvertAll(x =>{
				            	int totalTime = x.TotalTravelTime(e => _routeCost[e]);
				            	return new Route(x, totalTime);
				            });
		}

		public Route FindQuickestRoute(string start, string end) {
			return FindAllRoutes(start, end)
				.MinBy(x => x.TravelTime);
		}

		private void AddEdge(BidirectionalGraph<string, StationLink> graph,
		                     Dictionary<StationLink, int> distances, StationLink stationLink,
		                     int weight) {
			graph.AddVerticesAndEdge(stationLink);
			distances.Add(stationLink, weight);
		}

		public Route FindLongestRoute(string start, string end) {
			return FindAllRoutes(start, end)
				.MaxBy(x => x.TravelTime);
		}

		private IEnumerable<Station> GetIncomingStations(string stationName) {
			return _graph.InEdges(stationName)
				.ToList()
				.ConvertAll(route => new Station(route.Source));
		}

		private IEnumerable<Station> GetOutgoingStations(string stationName) {
			return _graph.OutEdges(stationName)
				.ToList()
				.ConvertAll(route => new Station(route.Target));
		}

		///This should really be done using Travelling Salesman Problem lib.
		public IEnumerable<RoundTripRoute> FindRoundTripRoutes(string start) {
			//find all incoming stations
			var station = FindStation(start);

			var result = new List<RoundTripRoute>();
			foreach (var incomingStation in station.IncomingStations) {
				IEnumerable<Route> routes = FindAllRoutes(start, incomingStation.Name);

				result.AddRange(
					routes.ToList()
						.ConvertAll<RoundTripRoute>(
						x =>{
							StationLink lastLink = new StationLink(incomingStation.Name, start);
							int lastLegTravelTime = _routeCost[lastLink];
							return new RoundTripRoute(x, lastLink, lastLegTravelTime);
						})
					);
				
			}

			return result;
		}
	}
}